#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os, os.path
import time

import log

from cellstore import CellStore
from help import insort,bisectLeft,bisectRight

class   CacheRevisionError(Exception) : pass

#######################################################################

class   CellCache(object) :
    def __init__(self, store_dir, capacity=8388608) :
        self.store_dir = store_dir
        self.capacity = capacity

        self.clear()
        # __init__()

    def __del__(self) :
        if self._content :
            try :
                self.dump()
            except :
                log.excpt()
        # __del__()

    def dump(self) :
        if not os.path.exists(self.store_dir) :
            os.makedirs(self.store_dir)

        store_filename_tmp = '/'.join((self.store_dir, self.revision))
        store = CellStore(store_filename_tmp)
        for _row_key in self._row_keys :
            records = self._content[_row_key]
            for key,data in records :
                store.append(key, data)
        del store
        store_filename = '.'.join((store_filename_tmp, 'data'))
        os.rename(store_filename_tmp, store_filename)

        # when finish dumping, clear status and data
        self.clear()
        # dump()

    def getDataRevision(self) :
        return 'c%.3f' % time.time()
        # getGeneth()

    def clear(self) :
        self._row_keys = []
        self._content = {}
        self._size = 0
        self.revision = self.getDataRevision()
        # clear()

    def insert(self, row_key, key, data) :
        if row_key in self._content :
            insort(self._content[row_key], (key,data))
        else :
            self._content[row_key] = [(key, data),]
        if row_key not in self._row_keys :
            insort(self._row_keys, row_key)

        self._size += len(key) + len(data)
        if self._size > self.capacity :
            try :
                self.dump()
            except :
                log.excpt()
            else :
                self.clear()

        # insert()

    # does CellCache have a 'erase' method??

    ###################################################################

    class   Scanner(object) :
        def __init__(self, cell_cache, context, timeout) :
            self.cell_cache = cell_cache
            self.revision = cell_cache.revision
            self._row_keys = cell_cache._row_keys[:]    # a snapshot for row_keys

            self.context = context
            self.timeout = timeout

            self._row_key_scan_current = None
            self._row_key_scan_current_index = 0

            self._key_scan_current = None
            self._key_scan_current_index = 0

            self.__prepared = False
            # __init__()

        def __iter__(self) :
            return self
            # __iter__()

        def next(self) :
            if self.revision != self.cell_cache.revision :
                raise CacheRevisionError, 'scanner-revision=%s cache-revision=%s' % (self.revision, self.cell_cache.revision)
            if not self._row_key_scan_current  :
                if not self.__prepared :
                    self._prescan()
                    self.__prepared = True
                if not self._row_key_scan_current :
                    raise StopIteration

            # locates current row-key
            records = self.cell_cache._content[self._row_key_scan_current]

            # locates current key
            key, data = records[self._key_scan_current_index]

            self._key_scan_current_index += 1
            if self._key_scan_current_index >= self._key_scan_end_index :
                self._row_key_scan_current_index += 1

                if self._row_key_scan_current_index >= self._row_key_scan_end_index :
                    self._row_key_scan_current = None
                else :
                    self._row_key_scan_current = self._row_keys[self._row_key_scan_current_index]

                    key_begin_index, key_end_index = self._calculateScanRange(self._row_key_scan_current)
                    self._key_scan_begin_index = key_begin_index
                    self._key_scan_end_index   = key_end_index
                    self._key_scan_current_index = self._key_scan_begin_index

            return key,data
            # next()

        def _prescan(self) :
            '''
            prepare for scan

            STEP. test if there are records would be selected

            '''
            if not self.context.scan_spec :
                raise StopIteration, 'Empty ScanSpec.'
            if not self.context.scan_spec.row_interval :
                raise StopIteration, 'Empty ScanSpec::RowInterval.'

            ri = self.context.scan_spec.row_interval
            index_low = bisectLeft(self._row_keys, ri.begin_row)
            self._row_key_scan_begin = self._row_keys[index_low]
            self._row_key_scan_begin_index = index_low

            index_up = bisectRight(self._row_keys, ri.end_row)
            if index_up == index_low :
                index_up += 1
            if index_up >= len(self._row_keys) :
                self._row_key_scan_end = None
                self._row_key_scan_end_index = len(self._row_keys)
            else :
                self._row_key_scan_end = self._row_keys[index_up]
                self._row_key_scan_end_index = index_up

            self._row_key_scan_current       = self._row_key_scan_begin
            self._row_key_scan_current_index = self._row_key_scan_begin_index

            key_begin_index, key_end_index = self._calculateScanRange(self._row_key_scan_current)
            self._key_scan_begin_index = key_begin_index
            self._key_scan_end_index   = key_end_index

            self._key_scan_current_index = self._key_scan_begin_index
            # _prescan()

        def _calculateScanRange(self, row_key) :
            ## todo : to calculate range according to TimeInterval
            begin_index = 0
            end_index   = len(self.cell_cache._content[row_key])

            return begin_index,end_index
            # _calculateScanRange()
        # class Scanner

    def createScanner(self, context, timeout=3600) :
        scanner = self.__class__.Scanner(self, context, timeout)

        return scanner
        # createScanner

    # class CellCache
